<?php 

/**
 * The main Class of Cinnamon Statistics Engine
 */

class Cinnamon {
	/** 
	 * The single instance of Cinnamon (singleton pattern) 
	 */
	private static $instance;
	
	/** 
	 * Variables in registry 
	 */
	private static $variables;
	
	/**
	 * This version of Cinnamon Statistics Engine software
	 */
	private static $version = '0.5.8';	
	
	/**
	 * The current mode of Cinnamon. There are six mode available: regular, javascript, track, ajax, install and upgrade. 
	 * The default mode is 'regular'. The javascript, track modes are automatically detected, while the
	 * ajax mode is set by the $_POST['csemode'] variable.
	 */
	private static $mode;
	
	/**
	 * The classpath where classes are located. Add your path to use your classes instant of default cinnamon classes
	 */
	private static $classpath = array(CSECORE);
	
	/**
	 * When the execution of the script started
	 */
	private static $timeStart;
	
	/**
	 * Protected constructor to ensure the single instance of this object
	 */
	protected function __construct() {
	}
	
	/**
	 * Returns the single instance of Cinnamon class
	 */
	public static function getInstance() {
		if ( !self::$instance ) {
			self::$instance = new Cinnamon();
		}
		return self::$instance;
	}

	/**
	 * Registers a variable, in order to be used later.
	 *
	 * @param string name the name of the variable
	 * @param mixed variable the value of the variable
	 */
	public static function register($name, $variable) {
		self::$variables[$name] = $variable;
	}

	/**
	 * Returns the value of a variable, that is stored in the registry
	 *
	 * @param string name the name of the variable
	 * @return mixed the value of the requested variable. If the value does not exists returns null
	 */	 	
	public static function registry($name) {
		return (self::$variables[$name]);
	}
	
	/**
	 * Returns the mode of Cinnamon. There are four mode available 'regular', 'javascript', 'track' and 'ajax'
	 * (Notice the lowercase)
	 * @return string the mode of Cinnamon
	 */
	public static function getMode() {
		return self::$mode;
	}
	
	/**
	 * Returns true if the cinnamon is in the specified mode(s).
	 * @param string mode the mode. This parameted can be repeated several times. For example:
	 * <code>Cinnamon::getInstance()->inMode('javascript', 'track')</code>
	 * @return Returns true only and only if the cinnamon is in the specified mode(s).
	 */
	public static function inMode() {
		foreach ( func_get_args() as $mode ) {
			if ( strcmp(self::$mode, $mode) == 0 ) {
				return true;
			} 
		}
		return false;
	}
	
	/**
	 * Loads the specified class. Use dots for directories. 
	 *
	 * @param string class the class name to be loaded
	 */
	public static function loadClass($class) {
		$originClass = $class;
	
		$class = basename($class);
		$class = str_replace('.', '/', $class);
		
		foreach ( self::$classpath as $path ) {
			$file = $path .'/'. $class .'.php';
			if ( file_exists($file) ) {
				require_once $file;
				return;
			}
		}
		
		throw new Exception('Class '. $originClass .' not found');
	}
	
	/**
	 * Sets the class path. Supports multiple paths, each path should be separated by ;
	 *
	 * @param string classpath the classpath to be used
	 */
	public static function setClasspath($classpath) {
		self::$classpath = explode(';', $classpath);
	}
	 
	/**
	 * Returns the class path. Supports multiple paths, each path should be separated by ;
	 *
	 * @param string classpath the classpath to be used
	 */	 
	public static function getClasspath() {
		return implode(';', self::$classpath);
	}
	 
	/**
	 * Returns the current version of Cinnamon Statistics Engine
	 *
	 * @return string the current version of Cinnamon Statistics Engine
	 */
	public static function getVersion() {
		return self::$version;
	}
	
	/**
	 * Returns the seconds spent for this request so far
	 *
	 * @return the generation time in seconds
	 */
	public static function getGenerationTime() {
		$now = explode(' ', microtime());
		$now = $now[1] + $now[0];
		return round( ($now - self::$timeStart), 2 );		
	}
	
	/**
	 * Smell the Cinnamon!!!
	 *
	 * This method is called after the definition of the basic configuration parameters in 'cse-config.php' file.
	 * The behaviour of this class is depended on the Cinnamon mode. 
	 * For example in other modes than 'regular' the Navigation instance is not created. 
	 * During the execution if this method, some of the core files are loaded and the corresponding classes instances are initialized. 
	 * Please note that those objects are stored in the registry.
	 */
	public static function smell() {
		// First of all create an instance of Cinnamon
		$cinnamon = self::getInstance();
		
		// Identify CSE mode
		// detect current mode
		// note: only ajax mode is support through $_POST
		if ( isset($_POST['csemode']) && strcmp($_POST['csemode'], 'ajax') == 0 ) {
			self::$mode = 'ajax';
		} else {	// check file names for tracking and javascript
			switch ( CSEFILE ) {
				case '/cse-javascript.php':
					self::$mode = 'javascript';
					break;			
				case '/cse-track.php':
					self::$mode = 'track';
					break;
				case '/admin/install.php':
					self::$mode = 'install';
					break;
				case '/admin/upgrade.php';
					self::$mode = 'upgrade';
					break;
				default:
					self::$mode = 'regular';
					break;
			}		
		}		
		
		// Start timer
		self::$timeStart = explode(' ', microtime());
		self::$timeStart = self::$timeStart[1] + self::$timeStart[0];
		
		// Set default classpath
		self::setClasspath(CSEPATH .'/core');
		
		// Avoid execution beyond this point in install mode
		if ( self::inMode('install') ) {
			return;
		}
		
		// Create event dispatcher
		self::loadClass('EventDispatcher');
		self::loadClass('Event');
		self::register('events', new EventDispatcher());
		
		// trigger initialize event. This event is very usefull if you want 
		// to overwrite the default core objects instances!
		self::registry('events')->dispatchEvent( new Event('preInitialize') );
		
		// Create database adapter -- take care of most common exceptions
		try {
			self::loadClass('DatabaseAdapter');
			self::register('db', new DatabaseAdapter(CSEDB_USER, CSEDB_PASS, CSEDB_HOST, CSEDB_NAME));
		} catch ( Exception $e ) {
			// avoid re-throwing the exception! 
			// otherwise username and password may be printed in plain text!
			
			// display nice messages in regular mode
			if ( self::inMode('regular') ) {
				self::loadClass('View');
				$view = new View();
				if ( $e->getCode() == 1044 )
					$view->render('db-select-error.php');
				else
					$view->render('db-connection-error.php');
				unset($view);
			}
			
			// I don't want to kill you, but I have to!
			die();
		}
		
		// Create configuration
		try {
			self::loadClass('Configuration');
			self::register('config', new Configuration( self::registry('db') ));
		} catch ( Exception $e ) {
			if ( $e->getCode() == 1146 ) {
				header('Location: '. CSEURL .'/admin/install.php');
				die();
			} 
			throw $e;
		}
		
		// Avoid execution beyond this point in upgrade mode
		if ( self::inMode('upgrade') ) {
			return;
		}
		
		// At this point check for database updates
		if ( strcmp(self::getVersion(), self::registry('config')->get('cse.version')) != 0 ) {
			header('Location: '. CSEURL .'/admin/upgrade.php?url='. urlencode($_SERVER['REQUEST_URI']));
			exit;
		}
		
		// Load and create session object based on mode
		if ( self::inMode('javascript', 'track') ) {
			self::loadClass('VisitorSession');
			self::register('session', new VisitorSession());		
		} else {
			self::loadClass('Session');
			self::register('session', new Session());
		}
		
		// Load and create core objects which are usefull only for regular and ajax mode
		if ( self::inMode('regular', 'ajax') ) {
			self::loadClass('View');
			self::register('view', new View());
		}
		
		// Load and create core object which are usefull only for regular mode
		if ( self::inMode('regular') ) {
			self::loadClass('Navigation');
			
			$menu = new Navigation('menu');
			$menu->addItem('/reports/index.php', 'Reports', true);
			$menu->addItem('/admin/plugins.php', 'Plugins', true);
			$menu->addItem('/admin/sites.php', 'Sites', true);
			$menu->addItem('/admin/options.php', 'Options', true);
			if ( self::registry('session')->isAuthenticated() ) {
				$menu->addItem('/cse-logout.php', 'Logout &raquo;', true);
			} else {
				$menu->addItem('/cse-login.php', 'Login &raquo;', false);
			}

			$menu->addItem('/reports/index.php', 'Dashboard', false, '/reports/index.php');
			$menu->addItem('/reports/content.php', 'Content', false, '/reports/index.php');
			$menu->addItem('/reports/traffic.php', 'Traffic', false, '/reports/index.php');
			$menu->addItem('/reports/visitors.php', 'Visitors', false, '/reports/index.php');
			$menu->addItem('/reports/spy.php', 'Spy', false, '/reports/index.php');
			self::register('menu', $menu);
		}
		
		// Load and create formmaters
		self::loadClass('Formatter');
		self::register('formats', new Formatter());
				
		// we don't like associative arrays, so let's convert $_GET and $_POST to an object
		$params = new stdClass();
		foreach ( array($_GET, $_POST) as $array ) {
			if ( count($array) > 0 ) {
				foreach ( $array as $name => $value ) {
					$params->{$name} = $value;
				}
			}
		}
		self::register('params', $params);
		
		// Load plugin instances
		self::loadClass('PluginManager');
		self::register('plugins', new PluginManager( self::registry('config') ));
		self::registry('events')->dispatchEvent( new Event('plugins') );	

		// Load filters
		self::loadClass('FilterManager');
		self::register('filters', new FilterManager());
		self::registry('events')->dispatchEvent( new Event('filters') );
		
		self::registry('events')->dispatchEvent( new Event('initialize') );
		
		// Check for websites status
		if ( Cinnamon::registry('session')->isAuthenticated() && Cinnamon::inMode('regular') ) {
			$sql = "SELECT COUNT(*) FROM ". Cinnamon::registry('db')->sites ." WHERE has_code <> 'y'";
			$count = Cinnamon::registry('db')->getValue($sql);
			if ( $count == 1 ) {
				Cinnamon::registry('view')->addMessage(View::WARNING, 'One of the websites does not contain Cinnamon tracking code, which is essential for gathering statistics. <a href="'. CSEURL .'/admin/sites.php">Learn more about this &raquo;</a>.');		
			} else if ( $count > 1 ) {
				Cinnamon::registry('view')->addMessage(View::WARNING, 'Some of the websites do not contain Cinnamon tracking code, which is essential for gathering statistics. <a href="'. CSEURL .'/admin/sites.php">Learn more about this &raquo;</a>.');		
			}
			unset($sql, $count);
		}
		
		// ok, we have smell the Cinnamon. Now we are ready to rock and roll! :)
		// trigger 'ready' begin!
		self::registry('events')->dispatchEvent( new Event('ready') );		
	}
}

?>